using System;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Web;

namespace Testing.Core
{
	class DatabaseTestContext : IDisposable
	{
		bool attached;
		readonly string connectionStringName;

		public DatabaseTestContext( string connectionStringName )
		{
			this.connectionStringName = connectionStringName;
		}

		public bool EnsureDatabase()
		{
			bool result = attached = ( File.Exists( ResolveDatabaseFilePath() ) || CreateDatabase() );
			return result;
		}

		bool CreateDatabase()
		{
			using ( SqlConnection connection = new SqlConnection( ContextString ) )
			{
				string text = ResolveCreateCommandText();
				using ( SqlCommand command = new SqlCommand( text, connection ) )
				{
					connection.Open();
					return command.ExecuteScalar() != null;
				}
			}
		}

		string ContextString
		{
			get { return contextString ?? ( contextString = ResolveContextString() ); }
		}	string contextString;
	
		void DetachDatabase()
		{
			using ( SqlConnection connection = new SqlConnection( ContextString ) )
			{
				try
				{
					connection.Open();
					new SqlCommand( ResolveKillProcessesCommand(), connection ).ExecuteNonQuery();
					SqlCommand command = new SqlCommand( "sp_detach_db", connection ) { CommandType = CommandType.StoredProcedure };
					command.Parameters.AddWithValue( "@dbname", ResolveDatabaseName() );
					command.Parameters.AddWithValue( "@skipchecks", "true" );
					command.ExecuteNonQuery();
				}
				catch ( Exception error )
				{
					Console.WriteLine( string.Format( "Error while Detaching database {0}: {1}", ResolveDatabaseName(), error ) );
				}
			}
		}
		
		string ResolveDatabaseName()
		{
			SqlConnectionStringBuilder builder = CreateBuilder();
			if ( !string.IsNullOrEmpty( builder.AttachDBFilename ) )
			{
				string path = builder.AttachDBFilename.Replace( "|DataDirectory|", VirtualPathUtility.AppendTrailingSlash( Environment.CurrentDirectory ) ),
					result = Path.GetFileNameWithoutExtension( path );
				return result;
			}
			return builder.InitialCatalog;
		}

		SqlConnectionStringBuilder CreateBuilder()
		{
			switch ( ConfigurationManager.ConnectionStrings[ connectionStringName ].ProviderName )
			{
				case "System.Data.EntityClient":
					EntityConnectionStringBuilder entity = new EntityConnectionStringBuilder( ConnectionString );
					SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder( entity.ProviderConnectionString );
					return builder;
				default:
					SqlConnectionStringBuilder result = new SqlConnectionStringBuilder( ConnectionString );
					return result;
			}
		}

		string ResolveContextString()
		{
			SqlConnectionStringBuilder builder = CreateBuilder();
			builder.InitialCatalog = string.Empty;
			builder.UserInstance = false;
			builder.AttachDBFilename = string.Empty;
			return builder.ConnectionString;
		}

		string ConnectionString
		{
			get  { return ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; }
		}

		string ResolveKillProcessesCommand()
		{
			string result = string.Format( @"
				USE master

				DECLARE @dbname sysname

				SET @dbname = '{0}'

				DECLARE @spid int
				SELECT @spid = min(spid) from master.dbo.sysprocesses where dbid = db_id(@dbname)
				WHILE @spid IS NOT NULL
				BEGIN
					EXECUTE ('KILL ' + @spid)
					SELECT @spid = min(spid) from master.dbo.sysprocesses where dbid = db_id(@dbname) AND spid > @spid
				END
				", ResolveDatabaseName() );
			return result;
		}

		string ResolveCreateCommandText()
		{
			string result = string.Format( @"
				IF NOT EXISTS( select * from sys.databases where name = '{0}' )
				BEGIN
					CREATE DATABASE ""{0}"" ON PRIMARY
					( NAME = '{0}_Data', 
					FILENAME = '{1}\{0}.mdf', 
					SIZE = 2MB, 
					MAXSIZE = 10MB, 
					FILEGROWTH = 10% ) 
					LOG ON 
					( NAME = '{0}_Log', 
					FILENAME = '{1}\{0}.ldf', 
					SIZE = 1MB, 
					MAXSIZE = 50MB, 
					FILEGROWTH = 10% )
					SELECT 1
				END", ResolveDatabaseName(), Environment.CurrentDirectory );
			return result;
		}

		string ResolveDatabaseFilePath()
		{
			string result = string.Format( @"{1}\{0}.mdf", ResolveDatabaseName(), Environment.CurrentDirectory );
			return result;
		}

		public void Dispose()
		{
			if ( attached )
			{
				DetachDatabase();
			}
		}
	}
}